package com.izhengyin.gateway.tracing;
import com.izhengyin.gateway.base.pojo.Api;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextKey;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.data.LinkData;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.StringUtils;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
public class TelemetryFactory {
    @Value("${spring.application.name}")
    private String application;
    final String NO_SAMPLED_SPAN_NAME = "NO_SAMPLED_SPAN_NAME";
    private final Tracer tracer;
    private static String HOST_NAME;

    static {
        try {
            InetAddress ia = InetAddress.getLocalHost();
            HOST_NAME = ia.getHostName();
        }catch (UnknownHostException e){}
    }
    public TelemetryFactory(){
        this.tracer = initTracer();
    }

    /**
     * 创建一个普通的span
     * @param api
     * @return
     */
    public Span createSpan(Api api){
        String name = application+" >>> "+api.getServer()+" >>> "+Optional.ofNullable(api.getName())
                .filter(v -> !StringUtils.isEmpty(v))
                .orElse(api.getMappingInfo());
        Span span = this.tracer.spanBuilder(name)
                .setSpanKind(SpanKind.SERVER)
                .setNoParent()
                .startSpan();
        span.setStatus(StatusCode.UNSET);
        span.setAttribute("server",api.getServer());
        span.setAttribute("method",api.getMethod());
        span.setAttribute("routing",api.getMappingInfo());
        return span;
    }

    /**
     * 创建一个不采样的span
     * @return
     */
    public Span createNoSampledSpan(){
        return this.tracer.spanBuilder(NO_SAMPLED_SPAN_NAME)
                .startSpan();
    }

    /**
     * 创建 w3c trace context
     * @param span
     * @return
     */
    public Context createW3CTraceContext(Span span){
        return new W3CTracePropagatorContextAdaptor(span);
    }

    /**
     * 初始化 Tracer
     * @return
     */
    private Tracer initTracer(){
        //阿里云报送使用
        SpanExporter grpcSpanExporter = OtlpGrpcSpanExporter.builder()
                .setEndpoint("https://xxx")
                .addHeader("x-sls-otel-project", "xxx")
                .addHeader("x-sls-otel-instance-id", "xxx")
                .addHeader("x-sls-otel-ak-id", "xxx")
                .addHeader("x-sls-otel-ak-secret", "xxx")
                .build();

        //测试使用
        SpanExporter spanExporter = new LoggingSpanExporter();

        Sampler sampler = new SpecifyNameSampler(NO_SAMPLED_SPAN_NAME);
        SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
                .addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
                .setResource(
                        Resource.create(
                                Attributes.builder()
                                        .put("service.name", application)
                                        .put("host.name", HOST_NAME).build()
                        )
                )
                .setSampler(sampler)
                .build();
        OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
                .setTracerProvider(sdkTracerProvider)
                .buildAndRegisterGlobal();
        return openTelemetry.getTracer("");
    }

    /**
     * 适配W3C传播的Context
     */
    private static class W3CTracePropagatorContextAdaptor implements Context {
        private final Span immutableSpan;
        public W3CTracePropagatorContextAdaptor(Span span){
            this.immutableSpan = span;
        }
        @SuppressWarnings("unchecked")
        public Span get(ContextKey contextKey) {
            return this.immutableSpan;
        }
        public <V> Context with(ContextKey<V> contextKey, V v) {
            throw new UnsupportedOperationException("W3CTracePropagatorContextAdaptor 不支持此操作");
        }
    }

    /**
     * 根据特定的Span名称作为判断是否采样的依据
     */
    private static class SpecifyNameSampler implements Sampler{
        private final String noSampledSpanName;
        public SpecifyNameSampler(String name){
            this.noSampledSpanName = name;
        }
        @Override
        public SamplingResult shouldSample(Context parentContext, String traceId, String name, SpanKind spanKind, Attributes attributes, List<LinkData> parentLinks) {
            if (name.equals(this.noSampledSpanName)){
                return SamplingResult.create(SamplingDecision.DROP);
            }
            return  SamplingResult.create(SamplingDecision.RECORD_AND_SAMPLE);
        }
        @Override
        public String getDescription() {
            return "SpecifyNameSampler";
        }
    }

    /**
     * 日志的 output
     */
    @Slf4j
    private static class LoggingSpanExporter implements SpanExporter {
        @Override
        public CompletableResultCode export(Collection<SpanData> collection) {
            for (SpanData spanData:collection){
                log.info("spanData : "+spanData.toString());
            }
            return CompletableResultCode.ofSuccess();
        }
        @Override
        public CompletableResultCode flush() {
            return CompletableResultCode.ofSuccess();
        }
        @Override
        public CompletableResultCode shutdown() {
            return CompletableResultCode.ofSuccess();
        }
        @Override
        public void close() {
            log.info(this.getClass().getName()+" , close!!!");
        }
    }
}
